# -*- coding: utf-8 -*-
import copy
import json
import io
import logging
import lxml.html
import datetime
import ast

from dateutil.relativedelta import relativedelta

try:
    from odoo.tools.misc import xlsxwriter
except ImportError:
    # TODO saas-17: remove the try/except to directly import from misc
    import xlsxwriter

from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, pycompat, config, date_utils


_logger = logging.getLogger(__name__)

import copy
from odoo import models, fields, api, _
from odoo.tools.safe_eval import safe_eval
from odoo.tools.misc import formatLang
from odoo.tools import float_is_zero, ustr

try:
    from odoo.addons.account_reports.models.account_financial_report import FormulaLine, FormulaContext
except ImportError:
    from addons.account_reports.models.account_financial_report import FormulaLine, FormulaContext

class AccountReport(models.AbstractModel):
    _inherit = 'account.report'

    @api.multi
    def get_html(self, options, line_id=None, additional_context=None):
        """
        Override
        Compute and return the content in HTML of the followup for the partner_id in options
        """
        if additional_context is None:
            additional_context = {}
        current_date_to = options.get('date')
        if options.get('date').get('date_to'):
            current_date_to = options.get('date').get('date_to')
        date_temp = fields.Date.from_string(current_date_to)
        additional_context['date_temp'] = date_temp.strftime('%Y年%m月')
        return super(AccountReport, self).get_html(options, line_id=line_id, additional_context=additional_context)

    def get_pdf(self, options, minimal_layout=True):
        # As the assets are generated during the same transaction as the rendering of the
        # templates calling them, there is a scenario where the assets are unreachable: when
        # you make a request to read the assets while the transaction creating them is not done.
        # Indeed, when you make an asset request, the controller has to read the `ir.attachment`
        # table.
        # This scenario happens when you want to print a PDF report for the first time, as the
        # assets are not in cache and must be generated. To workaround this issue, we manually
        # commit the writes in the `ir.attachment` table. It is done thanks to a key in the context.
        if not config['test_enable']:
            self = self.with_context(commit_assetsbundle=True)

        base_url = self.env['ir.config_parameter'].sudo().get_param('report.url') or self.env[
            'ir.config_parameter'].sudo().get_param('web.base.url')
        rcontext = {
            'mode': 'print',
            'base_url': base_url,
            'company': self.env.user.company_id,
        }

        body = self.env['ir.ui.view'].render_template(
            "account_reports.print_template",
            values=dict(rcontext),
        )
        body_html = self.with_context(print_mode=True).get_html(options)

        body = body.replace(b'<body class="o_account_reports_body_print">',
                            b'<body class="o_account_reports_body_print">' + body_html)
        if minimal_layout:
            header = ''
            footer = self.env['ir.actions.report'].render_template("web.internal_layout", values=rcontext)
            spec_paperformat_args = {'data-report-margin-top': 10, 'data-report-header-spacing': 10}
            footer = self.env['ir.actions.report'].render_template("web.minimal_layout",
                                                                   values=dict(rcontext, subst=True, body=footer))
        else:
            rcontext.update({
                'css': '',
                'o': self.env.user,
                'res_company': self.env.user.company_id,
            })
            header = self.env['ir.actions.report'].render_template("web.external_layout", values=rcontext)
            header = header.decode('utf-8')  # Ensure that headers and footer are correctly encoded
            spec_paperformat_args = {}
            # parse header as new header contains header, body and footer
            try:
                root = lxml.html.fromstring(header)
                match_klass = "//div[contains(concat(' ', normalize-space(@class), ' '), ' {} ')]"

                for node in root.xpath(match_klass.format('header')):
                    headers = lxml.html.tostring(node)
                    headers = self.env['ir.actions.report'].render_template("web.minimal_layout",
                                                                            values=dict(rcontext, subst=True,
                                                                                        body=headers))
                for node in root.xpath(match_klass.format('footer')):
                    footer = lxml.html.tostring(node)
                    footer = self.env['ir.actions.report'].render_template("web.minimal_layout",
                                                                           values=dict(rcontext, subst=True,
                                                                                       body=footer))
            except lxml.etree.XMLSyntaxError:
                headers = header
                footer = ''
            header = headers

        landscape = False
        if len(self.with_context(print_mode=True).get_header(options)[-1]) > 5:
            landscape = True

        return self.env['ir.actions.report']._run_wkhtmltopdf(
            [body],
            header=None, footer=None,
            landscape=landscape,
            specific_paperformat_args=spec_paperformat_args
        )

class AccountFinancialReport(models.Model):
    _inherit = "account.financial.html.report"
    account_type = fields.Selection([
        ('assets_liabilities', '资产负债表'),
        ('statement', '利润表'),
        ('cash_flow', '现金流量表')
    ], string="类型", help="区分中国财务三大报表")


    @api.multi
    def _get_lines(self, options, line_id=None):
        line_obj = self.line_ids
        if line_id:
            line_obj = self.env['account.financial.html.report.line'].search([('id', '=', line_id)])
        if options.get('comparison') and options.get('comparison').get('periods'):
            line_obj = line_obj.with_context(periods=options['comparison']['periods'])
        if options.get('ir_filters'):
            line_obj = line_obj.with_context(periods=options.get('ir_filters'))

        currency_table = self._get_currency_table()
        domain, group_by = self._get_filter_info(options)

        if group_by:
            options['groups'] = {}
            options['groups']['fields'] = group_by
            options['groups']['ids'] = self._get_groups(domain, group_by)

        amount_of_periods = len((options.get('comparison') or {}).get('periods') or []) + 1
        amount_of_group_ids = len(options.get('groups', {}).get('ids') or []) or 1
        linesDicts = [[{} for _ in range(0, amount_of_group_ids)] for _ in range(0, amount_of_periods)]
        if not self.account_type:
            res = line_obj.with_context(
                cash_basis=options.get('cash_basis'),
                filter_domain=domain,
            )._get_lines(self, currency_table, options, linesDicts)
        else:
            res = line_obj.with_context(
                cash_basis=options.get('cash_basis'),
                filter_domain=domain,
            )._get_lines_custome(self, currency_table, options, linesDicts)
        return res

class AccountFinancialReportLine(models.Model):
    _inherit = "account.financial.html.report.line"
    class_type = fields.Boolean('区分报表上的左右(默认在左)', default=True)

    def _divide_line(self, line):
        line1 = {
            'id': line['id'],
            'name': line['name'],
            'level': line['level'],
            'class': line['class'],
            'class_type': line['class_type'],
            'columns': [{'name': ''}] * len(line['columns']),
            'columns_pre': [{'name': ''}] * len(line['columns_pre']),
            'unfoldable': line['unfoldable'],
            'unfolded': line['unfolded'],
            'page_break': line['page_break'],
        }
        line2 = {
            'id': line['id'],
            'name': _('Total') + ' ' + line['name'],
            'class_type': line['class_type'],
            'class': 'total',
            'level': line['level'] + 1,
            'columns': line['columns'],
            'columns_pre': line['columns_pre'],
        }
        return [line1, line2]
    def _eval_formula(self, financial_report, debit_credit, currency_table, linesDict_per_group, groups=False):
        groups = groups or {'fields': [], 'ids': [()]}
        debit_credit = debit_credit and financial_report.debit_credit
        formulas = self._split_formulas()

        currency = self.env.user.company_id.currency_id

        line_res_per_group = []

        if not groups['ids']:
            return [{'line': {'balance': 0.0}}]

        # this computes the results of the line itself
        for group_index, group in enumerate(groups['ids']):
            self_for_group = self.with_context(group_domain=self._get_group_domain(group, groups))
            linesDict = linesDict_per_group[group_index]
            line = False
            if self._context.get('pre_line'):
                linesDict = {}
            if self.code and self.code in linesDict:
                line = linesDict[self.code]
            elif formulas and formulas['balance'].strip() == 'count_rows' and self.groupby:
                line_res_per_group.append({'line': {'balance': self_for_group._get_rows_count()}})
            elif formulas and formulas['balance'].strip() == 'from_context':
                line_res_per_group.append({'line': {'balance': self_for_group._get_value_from_context()}})
            else:
                line = FormulaLine(self_for_group, currency_table, financial_report, linesDict=linesDict)

            if line:
                res = {}
                res['balance'] = line.balance
                res['balance'] = currency.round(line.balance)
                if debit_credit:
                    res['credit'] = currency.round(line.credit)
                    res['debit'] = currency.round(line.debit)
                line_res_per_group.append(res)

        # don't need any groupby lines for count_rows and from_context formulas
        if all('line' in val for val in line_res_per_group):
            return line_res_per_group

        columns = []
        # this computes children lines in case the groupby field is set
        if self.domain and self.groupby and self.show_domain != 'never':
            if self.groupby not in self.env['account.move.line']:
                raise ValueError(_('Groupby should be a field from account.move.line'))

            groupby = [self.groupby or 'id']
            if groups:
                groupby = groups['fields'] + groupby
            groupby = ', '.join(['"account_move_line".%s' % field for field in groupby])

            aml_obj = self.env['account.move.line']
            tables, where_clause, where_params = aml_obj._query_get(domain=self._get_aml_domain())
            sql, params = self._get_with_statement(financial_report)
            if financial_report.tax_report:
                where_clause += ''' AND "account_move_line".tax_exigible = 't' '''

            select, select_params = self._query_get_select_sum(currency_table)
            params += select_params
            sql = sql + "SELECT " + groupby + ", " + select + " FROM " + tables + " WHERE " + where_clause + " GROUP BY " + groupby + " ORDER BY " + groupby

            params += where_params
            self.env.cr.execute(sql, params)
            results = self.env.cr.fetchall()
            for group_index, group in enumerate(groups['ids']):
                linesDict = linesDict_per_group[group_index]
                results_for_group = [result for result in results if group == result[:len(group)]]
                if results_for_group:
                    results_for_group = [r[len(group):] for r in results_for_group]
                    results_for_group = dict(
                        [(k[0], {'balance': k[1], 'amount_residual': k[2], 'debit': k[3], 'credit': k[4]}) for k in
                         results_for_group])
                    c = FormulaContext(self.env['account.financial.html.report.line'].with_context(
                        group_domain=self._get_group_domain(group, groups)),
                        linesDict, currency_table, financial_report, only_sum=True)
                    if formulas:
                        for key in results_for_group:
                            c['sum'] = FormulaLine(results_for_group[key], currency_table, financial_report,
                                                   type='not_computed')
                            c['sum_if_pos'] = FormulaLine(
                                results_for_group[key]['balance'] >= 0.0 and results_for_group[key] or {'balance': 0.0},
                                currency_table, financial_report, type='not_computed')
                            c['sum_if_neg'] = FormulaLine(
                                results_for_group[key]['balance'] <= 0.0 and results_for_group[key] or {'balance': 0.0},
                                currency_table, financial_report, type='not_computed')
                            for col, formula in formulas.items():
                                if col in results_for_group[key]:
                                    results_for_group[key][col] = safe_eval(formula, c, nocopy=True)
                    to_del = []
                    for key in results_for_group:
                        if self.env.user.company_id.currency_id.is_zero(results_for_group[key]['balance']):
                            to_del.append(key)
                    for key in to_del:
                        del results_for_group[key]
                    results_for_group.update({'line': line_res_per_group[group_index]})
                    columns.append(results_for_group)
                else:
                    res_vals = {'balance': 0.0}
                    if debit_credit:
                        res_vals.update({'debit': 0.0, 'credit': 0.0})
                    columns.append({'line': res_vals})

        return columns or [{'line': res} for res in line_res_per_group]

    @api.multi
    def _get_lines_custome(self, financial_report, currency_table, options, linesDicts):
        final_result_table = []
        comparison_table = [options.get('date')]
        comparison_table += options.get('comparison') and options['comparison'].get('periods', []) or []
        currency_precision = self.env.user.company_id.currency_id.rounding

        # build comparison table
        for line in self:
            res_pre = []
            res = []
            debit_credit = len(comparison_table) == 1
            domain_ids = {'line'}
            domain_ids_pre = {'line'}
            k = 0
            for period in comparison_table:
                report_type = financial_report.account_type
                current_date_to = options.get('date')
                if options.get('date').get('date_to'):
                    current_date_to = options.get('date').get('date_to')
                date_temp = fields.Date.from_string(current_date_to)
                current_year = date_temp.year
                last_year = current_year - 1
                date_from = period.get('date_from', False)
                current_date = period.get('date', False)
                if not current_date:
                    current_date = date_from
                if report_type == 'assets_liabilities':
                    pre_date_from = False
                    pre_date_to = str(last_year) + '-12-31'
                elif report_type == 'statement':
                    pre_date_from = str(current_year) + '-1-1'
                    pre_date_to = str(current_year) + '-12-31'
                elif report_type == 'cash_flow':
                    pre_date_from = str(current_year) + '-1-1'
                    pre_date_to = str(current_year) + '-12-31'

                date_to = period.get('date_to', False) or period.get('date', False)
                date_from, date_to, strict_range = line.with_context(date_from=date_from,
                                                                     date_to=date_to)._compute_date_range()
                pre_date_from, pre_date_to, pre_strict_range = line.with_context(date_from=pre_date_from,
                                                                                 date_to=pre_date_to)._compute_date_range()

                r = line.with_context(date_from=date_from,
                                      date_to=date_to,
                                      strict_range=strict_range)._eval_formula(financial_report,
                                                                               debit_credit,
                                                                               currency_table,
                                                                               linesDicts[k],
                                                                               groups=options.get('groups'))
                pre_r = line.with_context(date_from=pre_date_from,
                                          date_to=pre_date_to,
                                          strict_range=pre_strict_range,
                                          pre_line=True)._eval_formula(financial_report,
                                                                       debit_credit,
                                                                       currency_table,
                                                                       linesDicts[k],
                                                                       groups=options.get('groups'))

                debit_credit = False
                res.extend(r)
                res_pre.extend(pre_r)
                for column in r:
                    domain_ids.update(column)
                for pre_column in pre_r:
                    domain_ids_pre.update(pre_column)
                k += 1

            res = line._put_columns_together(res, domain_ids)
            res_pre = line._put_columns_together(res_pre, domain_ids_pre)

            if line.hide_if_zero and all(
                    [float_is_zero(k, precision_rounding=currency_precision) for k in res['line']]):
                continue
            if line.hide_if_zero and all(
                    [float_is_zero(k, precision_rounding=currency_precision) for k in res_pre['line']]):
                continue

            # Post-processing ; creating line dictionnary, building comparison, computing total for extended, formatting
            vals = {
                'id': line.id,
                'name': line.name,
                'level': line.level,
                'class': 'o_account_reports_totals_below_sections' if self.env.user.company_id.totals_below_sections else '',
                'class_type': line.class_type,
                'columns': [{'name': l} for l in res['line']],
                'columns_pre': [{'name': x} for x in res_pre['line']],
                'unfoldable': len(domain_ids) > 1 and line.show_domain != 'always',
                'unfolded': line.id in options.get('unfolded_lines', []) or line.show_domain == 'always',
                'page_break': line.print_on_new_page,
            }

            if financial_report.tax_report and line.domain and not line.action_id:
                vals['caret_options'] = 'tax.report.line'

            if line.action_id:
                vals['action_id'] = line.action_id.id
            domain_ids.remove('line')
            domain_ids_pre.remove('line')
            lines = [vals]
            groupby = line.groupby or 'aml'
            if line.id in options.get('unfolded_lines', []) or line.show_domain == 'always':
                if line.groupby:
                    domain_ids = sorted(list(domain_ids), key=lambda k: line._get_gb_name(k))
                for domain_id in domain_ids:
                    name = line._get_gb_name(domain_id)
                    if not self.env.context.get('print_mode') or not self.env.context.get('no_format'):
                        name = name[:40] + '...' if name and len(name) >= 45 else name
                    vals = {
                        'id': domain_id,
                        'name': name,
                        'level': line.level,
                        'parent_id': line.id,
                        'columns': [{'name': l} for l in res[domain_id]],
                        'columns_pre': [{'name': x} for x in res_pre[domain_id]],
                        'caret_options': groupby == 'account_id' and 'account.account' or groupby,
                    }
                    if line.financial_report_id.name == 'Aged Receivable':
                        vals['trust'] = self.env['res.partner'].browse([domain_id]).trust
                    lines.append(vals)
                if domain_ids and self.env.user.company_id.totals_below_sections:
                    lines.append({
                        'id': 'total_' + str(line.id),
                        'name': _('Total') + ' ' + line.name,
                        'level': line.level,
                        'class': 'o_account_reports_domain_total',
                        'parent_id': line.id,
                        'columns': copy.deepcopy(lines[0]['columns']),
                        'columns_pre': copy.deepcopy(lines[0]['columns_pre']),
                    })

            for vals in lines:
                if len(comparison_table) == 2 and not options.get('groups'):
                    vals['columns'].append(line._build_cmp(vals['columns'][0]['name'], vals['columns'][1]['name']))
                    vals['columns_pre'].append(
                        line._build_cmp(vals['columns_pre'][0]['name'], vals['columns_pre'][1]['name']))
                    for i in [0, 1]:
                        vals['columns'][i] = line._format(vals['columns'][i])
                        vals['columns_pre'][i] = line._format(vals['columns_pre'][i])
                else:
                    vals['columns'] = [line._format(v) for v in vals['columns']]
                    vals['columns_pre'] = [line._format(n) for n in vals['columns_pre']]
                if not line.formulas:
                    vals['columns_pre'] = [{'name': ''} for k in vals['columns_pre']]
                    vals['columns'] = [{'name': ''} for k in vals['columns']]

            if len(lines) == 1:
                new_lines = line.children_ids._get_lines_custome(financial_report, currency_table, options, linesDicts)
                if new_lines and line.formulas:
                    if self.env.user.company_id.totals_below_sections:
                        divided_lines = self._divide_line(lines[0])
                        result = [divided_lines[0]] + new_lines + [divided_lines[-1]]
                    else:
                        result = [lines[0]] + new_lines
                else:
                    result = lines + new_lines
            else:
                result = lines
            final_result_table += result

        return final_result_table
